package org.xukai.common.spel.spel;

import com.alibaba.fastjson.JSON;
import org.apache.log4j.Logger;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.xukai.common.spel.AbstractFieldResolver;
import org.xukai.common.spel.ArrayFieldConfig;
import org.xukai.common.spel.ComboDetailFiled;
import org.xukai.common.spel.ComboFieldConfig;
import org.xukai.common.spel.FieldConfig;
import org.xukai.common.spel.FieldConfigMapBuilder;
import org.xukai.common.spel.IKeyTranslater;
import org.xukai.common.spel.ListFieldConfig;
import org.xukai.common.spel.SumListFieldConfig;
import org.xukai.common.spel.TVEException;
import org.xukai.common.spel.utils.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author JackyLIU
 */
public class SpElFieldResolver extends AbstractFieldResolver {

    private final Logger logger = Logger.getLogger(this.getClass());

    /**
     * 当传入了key在配置中不存在，则抛出异常
     */
    private boolean missKeyOnFail = false;

    /**
     * 当传入了domain在配置中不存在，则抛出异常
     */
    private boolean missDomainOnFail = false;
    
    /**
     * 当属性值为null时，默认设置为""字符串
     */
    private boolean missValueOnEmpty = true;

    @Override
    public Map<String, Object> resolve(List<String> keys, Object... domains)
            throws TVEException {
    	SpelParserConfiguration config = new SpelParserConfiguration(true, true);
        ExpressionParser parser = new SpelExpressionParser(config);
        StandardEvaluationContext context;
        try {
            context = createStandardEvaluationContext(findFieldConfig(keys), domains);
        } catch (Exception e1) {
            logger.debug("TVEervice createStandardEvaluationContext" + e1.getMessage() + "domain:" + JSON.toJSONString(domains));
            throw new TVEException(e1);
        }

        Map<String, Object> results = new HashMap<String, Object>();
        try {
            for (String key : keys) {
                Object result = parse(parser, context, key);
                if (null == result) {
                	if(missValueOnEmpty){
                        results.put(key, "");
                	}
                    continue;
                }
                results.put(key, result);
            }
        } catch (Exception e) {
            throw new TVEException(e);
        }
        return results;
    }

    private Object parse(ExpressionParser parser, StandardEvaluationContext context, String key) {
        String el = keyTranslater.translate(key);
        if (null == el)
            return null;

        FieldConfig<?> config = keyTranslater.getFieldConfig(key);
        // 先判断一下config的类型
        if (config instanceof ArrayFieldConfig) {
            return parseArrayFieldConfig(parser, context, (ArrayFieldConfig<?>) config, el);
        }
        if (config instanceof SumListFieldConfig) {
            return parseSumListFieldConfig(parser, context, (SumListFieldConfig<?>) config, el);
        }

        if (config instanceof ListFieldConfig) {
            return parseListFieldConfig(parser, context, (ListFieldConfig<?>) config, el);
        }

        if (config instanceof ComboFieldConfig) {
            return parseComboFieldConfig(parser, context, (ComboFieldConfig<?>) config, el);
        }

        return parseFieldConfig(parser, context, config, el);
    }

    private Object parseFieldConfig(ExpressionParser parser, StandardEvaluationContext context, FieldConfig<?> config, String el){

        return parser.parseExpression(el).getValue(context);
    }

    private Object parseArrayFieldConfig(ExpressionParser parser, StandardEvaluationContext context, ArrayFieldConfig<?> config, String el) {

        String __el = el;
        if (config.isInline()) {
//            __el = new StringBuilder("#join(").append(__el).append(",'").append(config.getSeperateChar()).append("')").toString();
            // output to a line of string
            return parseListResultInline(parser, context, config.getSeperateChar(), el);
        }

        // parse to a list
        return parser.parseExpression(__el).getValue(context);
    }

    private Object parseListFieldConfig(ExpressionParser parser, StandardEvaluationContext context, ListFieldConfig<?> config, String el) {

        String __el = el;
        if (config.isInline()) {
//            __el = new StringBuilder("#join(").append(__el).append(",'").append(config.getSeperateChar()).append("')").toString();
            // output to a line of string
            return parseListResultInline(parser, context, config.getSeperateChar(), el);
        }

        // parse to a list
        return parser.parseExpression(__el).getValue(context);
    }

    @SuppressWarnings("unchecked")
	private Object parseSumListFieldConfig(ExpressionParser parser, StandardEvaluationContext context, SumListFieldConfig<?> config, String el) {

        if (config.getObject() == Integer.class) {
            List<Integer> list = (List<Integer>) parser.parseExpression(el).getValue(context);
            Integer along = 0;
            for (Integer aLong : list) {
                along += aLong;
            }
            return along;
        } else if (config.getObject() == Double.class) {
            List<String> list = (List<String>) parser.parseExpression(el).getValue(context);
            BigDecimal along = new BigDecimal(0);
            for (String aLong : list) {
                along = along.add(new BigDecimal(Double.parseDouble(aLong)));
            }
            return along;
        }
        return "N/A";
    }


    private Object parseComboFieldConfig(ExpressionParser parser, StandardEvaluationContext context, ComboFieldConfig<?> config, String el) {

        String __el = el;
        return parser.parseExpression(__el).getValue(context);
    }


    private List<FieldConfig<?>> findFieldConfig(List<String> keys) throws TVEException {
        List<FieldConfig<?>> fieldConfigs = new ArrayList<FieldConfig<?>>(keys.size());
        for (String key : keys) {
            if (!keyTranslater.getFieldConfigs().containsKey(key)) {
                String error = new StringBuilder("can't find fieldConfig with key:").append(key).toString();
                if (missKeyOnFail) {
                    throw new TVEException(error);
                }
                continue;
            }
            fieldConfigs.add(keyTranslater.getFieldConfig(key));
        }
        return fieldConfigs;
    }

    /**
     * 创建SPEL需要的解析器上下文，并将相关的变量设置到上下文中
     *
     * @param fieldConfigs
     * @param domains
     * @return
     * @throws TVEException
     * @throws NoSuchMethodException
     * @throws SecurityException
     */
    private StandardEvaluationContext createStandardEvaluationContext(List<FieldConfig<?>> fieldConfigs, Object... domains) throws TVEException, SecurityException, NoSuchMethodException {
        StandardEvaluationContext context = new StandardEvaluationContext();
        Map<Class<?>, Object> classInfos = toClassInfos(domains);

        List<FieldConfig<?>> __fieldConfigs = filterSameClass(fieldConfigs);

        for (FieldConfig<?> fieldConfig : __fieldConfigs) {
            if (!classInfos.containsKey(fieldConfig.getDomainClazz())) {
                String error = new StringBuilder("can't find domain object with class:").append(fieldConfig.getClass()).toString();
                logger.warn(error);
                if (missDomainOnFail) {
                    throw new TVEException(error);
                }
                continue;
            }

            if (fieldConfig instanceof ComboFieldConfig) {
                ComboFieldConfig<?> comboFieldConfig = (ComboFieldConfig<?>) fieldConfig;
                for (Object obj : comboFieldConfig.getComboFieldConfigs()) {
                    ComboDetailFiled<?> comboDetailFiled = (ComboDetailFiled<?>) obj;
                    if (comboDetailFiled.getUseAnotherDomain()) {
                        context.setVariable(comboDetailFiled.getAnotherDomainName(), classInfos.get(comboDetailFiled.getDomainClazz()));

                    }
                }
            }
            context.setVariable(fieldConfig.getDomainName(), classInfos.get(fieldConfig.getDomainClazz()));

        }

        context.registerFunction("join", StringUtils.class.getMethod("join", List.class, char.class));
        return context;
    }

    /**
     * 过滤掉包含有相同的class信息的fieldConfig
     *
     * @param fieldConfigs
     * @return
     */
    protected List<FieldConfig<?>> filterSameClass(Collection<FieldConfig<?>> fieldConfigs) {
        List<FieldConfig<?>> filter = new ArrayList<FieldConfig<?>>();
        Map<Class<?>, Integer> exists = new HashMap<Class<?>, Integer>();
        for (FieldConfig<?> config : fieldConfigs) {
            if (exists.containsKey(config.getDomainClazz())) {
                continue;
            }
            exists.put(config.getDomainClazz(), 1);
            filter.add(config);
        }
        return filter;
    }

    private Map<Class<?>, Object> toClassInfos(Object... domains) {
        Map<Class<?>, Object> classInfos = new HashMap<Class<?>, Object>(domains.length);
        for (Object domain : domains) {
            classInfos.put(domain.getClass(), domain);
            findParentClass(domain.getClass(), classInfos, domain);
        }
        return classInfos;
    }
    
    private Class<?> findParentClass(Class<?> clazz, Map<Class<?>, Object> classInfos, Object domain){
    	if(clazz.getSuperclass() == Object.class){
    		return clazz;
    	}

        classInfos.put(clazz.getSuperclass(), domain);
    	return findParentClass(clazz.getSuperclass(), classInfos, domain);
    }

    private Class<?> findParentClass(Class<?> clazz){
    	if(clazz.getSuperclass() == Object.class){
    		return clazz;
    	}

    	return findParentClass(clazz.getSuperclass());
    }
    public SpElFieldResolver(IKeyTranslater keyTranslater) {
        super(keyTranslater);
    }

    public SpElFieldResolver(Map<String, FieldConfig<?>> fieldConfigs) {
        super(new SpElKeyTranslater(fieldConfigs));
    }

    public SpElFieldResolver(FieldConfigMapBuilder builder) {
        super(new SpElKeyTranslater(builder));
    }

    /**
     * 当传入了key在配置中不存在，则抛出异常
     *
     * @return
     */
    public boolean isMissKeyOnFail() {
        return missKeyOnFail;
    }

    /**
     * 当传入了key在配置中不存在，则抛出异常
     *
     * @param missKeyOnFail
     */
    public void setMissKeyOnFail(boolean missKeyOnFail) {
        this.missKeyOnFail = missKeyOnFail;
    }

    /**
     * 当传入了domain在配置中不存在，则抛出异常
     *
     * @return
     */
    public boolean isMissDomainOnFail() {
        return missDomainOnFail;
    }

    public boolean isMissValueOnEmpty() {
        return missValueOnEmpty;
    }

    public void setMissValueOnEmpty(boolean missValueOnEmpty) {
        this.missValueOnEmpty = missValueOnEmpty;
    }

    /**
     * 当传入了domain在配置中不存在，则抛出异常
     *
     * @param missDomainOnFail
     */
    public void setMissDomainOnFail(boolean missDomainOnFail) {
        this.missDomainOnFail = missDomainOnFail;
    }

    /**
     * 判断该object是否为空
     * @param object
     * @return
     */
    private boolean isEmpty(Object object){
        if(object == null){
            return true;
        }
        if(object instanceof String){
            return StringUtils.isEmpty(String.valueOf(object));
        }

        return false;
    }

    /**
     * 是否使用subFieldName进行重新翻译表达式
     * @param object
     * @param config
     * @return
     */
    private boolean useSubFieldTranslate(Object object, FieldConfig<?> config){
        if(StringUtils.isEmpty(config.getSubFieldName())){
            return false;
        }
        if(!isEmpty(object)){
            return false;
        }

        return true;
    }

    private Object parseListResultInline(ExpressionParser parser, StandardEvaluationContext context, char seperateChar, String el){
        Object result = parser.parseExpression(el).getValue(context);
        if(result instanceof List){
            List list = (List)result;
            try {
                return StringUtils.join(list, seperateChar);
            }catch (Exception e){
                logger.error("spel list join error:"+el, e);
                return parser.parseExpression(el).getValue(context, String.class);
            }
        }
        return parser.parseExpression(el).getValue(context, String.class);
    }
}
